﻿using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using LitJson;
using TableCore.Exceptions;
using TableCore.Plugin;

namespace TableCore
{
    public enum ECaseType
    {
        normal,
        upper,
        lower,
    }

    public class GTType : IGenFormatter
    {
        readonly static string defulatInput = "{name} = ({type}){input}[\"{name}\"];";
        readonly static int declareid = StringUtil.IgnoreCaseToHash("declare");
        readonly static int formaterid = StringUtil.IgnoreCaseToHash("formatter");
        readonly static int namespaceid = StringUtil.IgnoreCaseToHash("using");
        readonly static int defaultid = StringUtil.IgnoreCaseToHash("default");
        readonly static int jsonid = StringUtil.IgnoreCaseToHash("json-input");

        readonly static int aliasid = StringUtil.IgnoreCaseToHash("alias");
        //readonly static int bininput = StringUtil.IgnoreCaseToHash("binary-input");
        //readonly static int binoutput = StringUtil.IgnoreCaseToHash("binary-output");
        readonly static int caseid = StringUtil.IgnoreCaseToHash("case");

        public ECaseType CaseType { get; private set; }
        //public EGTLang Lang { get; private set; }
        public string Name { get; private set; }
        public string DefaultValue { get; private set; }
        /// <summary>
        /// 生成对象类型名
        /// </summary>
        public string DeclareName { get; private set; }
        public IGenFormatter GenFormatter { get; private set; }
        public string JsonInput { get; private set; }

        Dictionary<string, string> mAlias;
        //public string BinaryInput { get; private set; }
        //public string BinaryOutput { get; private set; }
        Regex mPattern;
        public Regex Pattern { get { return mPattern; } }
        bool mIsValid;
        string[] mNamespace;
        public int NamespaceCount { get { return mNamespace == null ? 0 : mNamespace.Length; } }
        public string GetNamespace(int index)
        {
            return mNamespace[index];
        }
        //public GTType(EGTLang lang)
        //{
        //    Lang = lang;
        //}

        public GTType(string typeName)
        {
            Name = typeName;
        }

        public void Initialize()
        {
            string file = null;
            var lib = Factory.LibFolder;
            if (!string.IsNullOrEmpty(lib))
            {
                file = Path.Combine(lib, $"{Name}.txt");
            }
            if (!string.IsNullOrEmpty(file) && File.Exists(file))
            {
                ParseInitFile(file);
            }
            else
            {
                file = Utils.GetRelativePath($"Config/Types/{Name}.txt");
                if (File.Exists(file))
                {
                    ParseInitFile(file);
                }
            }
            //else
            //{
            //    file = Utils.GetRelativePath(string.Format("Config/Types/{0}-input-csharp.txt", Name));
            //    if (File.Exists(file))
            //    {
            //        JsonInput = File.ReadAllText(file);
            //        mIsValid = true;
            //    }
            //}
            //Debug.LogFormat("Setup Type: {0}", Name);
        }

        public bool IsValidType() { return mIsValid; }

        void ParseInitFile(string file)
        {
            var reader = new SitcomFile();
            reader.OperatorFilter = (c) => c == '@' || c == ':';
            reader.BracketFilter = (c) => c == '(' || c == ')' || c == '（' || c == '）';
            reader.Load(File.ReadAllText(file));
            reader.BeginRead();
            if (FindCmd(reader, declareid) && reader.NextKeyword())
            {
                if (reader.keyword.id != ':' && reader.keyword.id != '：')
                {
                    DeclareName = Name;
                }
                else if (reader.NextKeyword())
                {
                    DeclareName = reader.keyword.text;
                    int key;
                    string value;
                    while (NextKeyValue(reader, out key, out value))
                    {
                        if (key == defaultid)
                            DefaultValue = value;
                        else if (key == caseid)
                            CaseType = StringUtil.EqualIgnoreCase(value, "lower") ? ECaseType.lower : (StringUtil.EqualIgnoreCase(value, "upper") ? ECaseType.upper : ECaseType.normal);
                    }
                }
                else
                {
                    DeclareName = Name;
                }
                if (reader.NextContent(true))
                    mPattern = new Regex(reader.keyword.text.Trim());
            }
            if (FindCmd(reader, formaterid) && reader.NextKeyword())
            {
                if ((reader.keyword.id == ':' || reader.keyword.id == '：') && reader.NextKeyword())
                {
                    var formatter = Utils.NewInstance(reader.keyword.text, true) as IGenFormatter;
                    if (formatter == null)
                    {
                        throw new TypeDefineException(Name, "formatter");
                    }
                    if (formatter is IGenCmdInitializer)
                    {
                        string arg, value;
                        Dictionary<string, string> args = new Dictionary<string, string>();
                        while (NextKeyValue(reader, out arg, out value))
                        {
                            args[arg] = value;
                        }
                        var content = reader.NextContent(true) ? reader.keyword.text : null;
                        ((IGenCmdInitializer)formatter).Init(this, args, content);
                    }
                    GenFormatter = formatter;
                }
            }
            else
            {
                GenFormatter = new StringFormatter();// new RawDataFormatter();
            }
            if(FindCmd(reader, namespaceid) && reader.NextContent(true) && reader.keyword.length > 0)
            {
                mNamespace = reader.keyword.text.Split('\n');
                for (int i = 0; i < mNamespace.Length; i++)
                {
                    mNamespace[i] = mNamespace[i].Trim();
                }
            }

            if(FindCmd(reader, aliasid) && reader.NextContent(true) && reader.keyword.length > 0)
            {
                ParseAlias(reader.keyword.text);
            }
            
            if (FindCmd(reader, jsonid) && reader.NextContent(true))
                JsonInput = reader.keyword.text;
            else
                JsonInput = defulatInput.Replace("{type}", DeclareName);

            //if (FindCmd(reader, bininput) && reader.NextContent(true))
            //    BinaryInput = reader.keyword.text;
            //if (FindCmd(reader, binoutput) && reader.NextContent(true))
            //    BinaryOutput = reader.keyword.text;
            mIsValid = !string.IsNullOrEmpty(DeclareName);
            //Debug.LogFormat("Load Type: {0}", Name);
        }

        void ParseAlias(string content)
        {
            mAlias = new Dictionary<string, string>();
            //var aliasCmdId = StringUtil.IgnoreCaseToHash("#alias#");
            var reader = new SitcomFile();
            reader.Load(content);
            reader.BeginRead();
            while (reader.StartLine())
            {
                //if (!reader.NextKeyword())
                //    continue;
                //var keyword = reader.keyword;
                //var isAlias = keyword.id == aliasCmdId;
                //if (isAlias)
                //{
                while (!reader.Eof)
                {
                    string var = null;
                    SitcomFile.Keyword result = default;
                    if (reader.NextKeyword())
                    {
                        var keyword = reader.keyword;
                        if (keyword.type == KeywordType.Content || keyword.type == KeywordType.Keyword)
                            var = keyword.text;
                        else
                            throw new InvalidFormulaException(reader.ToString());
                    }
                    else
                    {
                        break;
                    }
                    if (!reader.NextKeyword() || reader.keyword.id != (int)'=')
                    {
                        throw new InvalidFormulaException(reader.ToString());
                    }
                    if (reader.NextKeyword())
                    {
                        var keyword = reader.keyword;
                        if (keyword.type == KeywordType.Keyword || keyword.type == KeywordType.Content)
                            result = keyword;
                        else
                            throw new InvalidFormulaException(reader.ToString());
                    }
                    else
                    {
                        throw new InvalidFormulaException(reader.ToString());
                    }
                    mAlias[var] = result.text;
                }
                //}
                //else
                //{
                //    throw new InvalidFormulaException(reader.ToString());
                //}
            }
        }

        bool FindCmd(SitcomFile file, int cmd)
        {
            var line = file.PresentLine;
            while(file.NextMark('@'))
            {
                if (file.NextKeyword() && file.keyword.id == cmd)
                    return true;
            }
            file.BeginRead();
            while(file.PresentLine < line && file.NextMark('@'))
            {
                if (file.NextKeyword() && file.keyword.id == cmd)
                    return true;
            }
            return false;
        }

        bool NextKeyValue(SitcomFile file, out int key, out string value)
        {
            key = 0;
            value = null;
            if (file.NextKeyword())
            {
                key = file.keyword.id;
                if (!file.NextKeyword())
                    return false;
                if (file.keyword.id != ':' && file.keyword.id != '：')
                    return false;
                if (!file.NextKeyword())
                    return false;
                value = file.keyword.text;
                return true;
            }
            return false;
        }

        bool NextKeyValue(SitcomFile file, out string arg, out string value)
        {
            arg = null;
            value = null;
            if(file.NextKeyword())
            {
                arg = file.keyword.text;
                if (!file.NextKeyword())
                    return false;
                if (file.keyword.id != ':' && file.keyword.id != '：')
                    return false;
                if (!file.NextKeyword())
                    return false;
                value = file.keyword.text;
                return true;
            }
            return false;
        }

        public string FormatInput(string input)
        {
            string v = string.IsNullOrEmpty(input) ? DefaultValue : input;
            if (v == null)
                return null;
            if (CaseType == ECaseType.upper)
                v = v.ToUpper();
            else if (CaseType == ECaseType.lower)
                v = v.ToLower();
            string alias;
            if (mAlias != null && mAlias.TryGetValue(v, out alias))
                v = alias;
            return v;
        }

        public bool IsValid(string input)
        {
            if (!string.IsNullOrEmpty(input) && mPattern != null && !mPattern.IsMatch(input)) // Regex.IsMatch(input, mPattern))
                return false;
            if (GenFormatter == null)
                return true;
            else
                return GenFormatter.IsValid(input);
        }

        public JsonData Format(GTFile.Content content, string input, string cellName, string comment)
        {
            if (GenFormatter == null)
                return input;
            else
                return GenFormatter.Format(content, input, cellName, comment);
        }
        
        public IExportData ExportData(GTFile.Content content, string data, string cellName, string comment)
        {
            return GenFormatter == null ? null : GenFormatter.ExportData(content, data, cellName, comment);
        }
    }
}
